/* eslint-disable no-useless-escape */
/* eslint-disable no-useless-catch */
/* eslint-disable no-redeclare */
/* eslint-disable @typescript-eslint/no-use-before-define */
/* eslint-disable max-len */
/* eslint-disable no-var */
import mxgraph from 'src/pages/mxgraph/graph/index';
import Actions from 'src/pages/mxgraph/graph/Actions';
import { Graph } from 'src/pages/mxgraph/graph/Graph';
import Editor, { Dialog, ErrorDialog, OpenFile } from 'src/pages/mxgraph/graph/Editor';

const {
  mxConstants,
  mxEvent,
  mxPoint,
  mxEventObject,
  mxObjectCodec,
  mxUtils,
  mxCodecRegistry,
  mxResources,
  mxEventSource,
  mxClient,
  mxPopupMenu,
  mxKeyHandler,
  mxClipboard,
  mxRectangle,
  mxOutline,
  mxMorphing,
} = mxgraph;

/**
 * Constructs a new graph editor
 */
const EditorUi = function (editor, container, lightbox ) {
  mxEventSource.call(this);

  this.destroyFunctions = [];
  this.editor = editor || new Editor();
  this.container = container || document.body;
  console.log();

  let graph = this.editor.graph;
  graph.lightbox = lightbox;

  //// Faster scrollwheel zoom is possible with CSS transforms
  if (graph.useCssTransforms) {
    this.lazyZoomDelay = 0;
  }

  //// Pre-fetches submenu image or replaces with embedded image if supported
  if (mxClient.IS_SVG) {
    mxPopupMenu.prototype.submenuImage =
      '';
  } else {
    new Image().src = mxPopupMenu.prototype.submenuImage;
  }

  //// Pre-fetches connect image
  //// if (!mxClient.IS_SVG && mxConnectionHandler.prototype.connectImage != null) {
  ////   new Image().src = mxConnectionHandler.prototype.connectImage.src;
  //// }

  //// Disables graph and forced panning in chromeless mode
  if (this.editor.chromeless && !this.editor.editable) {
    graph.isEnabled = function () {
      return false;
    };
    graph.panningHandler.isForcePanningEvent = function (me) {
      return !mxEvent.isPopupTrigger(me.getEvent());
    };
  }

  //// Creates the user interface
  this.actions = new Actions(this);
  this.createDivs();
  this.createUi();
  this.refresh();

  //// Disables HTML and text selection
  let textEditing = mxUtils.bind(this, function (evt) {
    if (evt == null) {
      evt = window.event;
    }

    return graph.isEditing() || (evt != null && this.isSelectionAllowed(evt));
  });

  //// Disables text selection while not editing and no dialog visible
  if (this.container == document.body) {
    this.diagramContainer.onselectstart = textEditing;
    this.diagramContainer.onmousedown = textEditing;

    if (this.tabContainer != null) {
      //// Mouse down is needed for drag and drop
      this.tabContainer.onselectstart = textEditing;
    }
  }

  //// And uses built-in context menu while editing
  if (!this.editor.chromeless || this.editor.editable) {
    //// Allows context menu for links in hints
    var linkHandler = function (evt) {
      if (evt != null) {
        var source = mxEvent.getSource(evt);

        if (source.nodeName == 'A') {
          while (source != null) {
            if (source.className == 'geHint') {
              return true;
            }

            source = source.parentNode;
          }
        }
      }

      return textEditing(evt);
    };

    if (
      mxClient.IS_IE &&
      (typeof document.documentMode === 'undefined' ||
        document.documentMode < 9)
    ) {
      mxEvent.addListener(this.diagramContainer, 'contextmenu', linkHandler);
    } else {
      //// Allows browser context menu outside of diagram and sidebar
      this.diagramContainer.oncontextmenu = linkHandler;
    }
  } else {
    graph.panningHandler.usePopupTrigger = false;
  }

  //// Contains the main graph instance inside the given panel
  graph.init(this.diagramContainer);

  //// Improves line wrapping for in-place editor
  if (mxClient.IS_SVG && graph.view.getDrawPane() != null) {
    var root = graph.view.getDrawPane().ownerSVGElement;

    if (root != null) {
      root.style.position = 'absolute';
    }
  }

  //// Creates hover icons

  //// Adds tooltip when mouse is over scrollbars to show space-drag panning option

  //// Escape key hides dialogs, adds space+drag panning
  var spaceKeyPressed = false;

  //// Forces panning for middle and right mouse buttons
  let panningHandlerIsForcePanningEvent =
    graph.panningHandler.isForcePanningEvent;
  graph.panningHandler.isForcePanningEvent = function (me) {
    //// Ctrl+left button is reported as right button in FF on Mac
    return (
      panningHandlerIsForcePanningEvent.apply(this, arguments) ||
      spaceKeyPressed ||
      (mxEvent.isMouseEvent(me.getEvent()) &&
        (this.usePopupTrigger || !mxEvent.isPopupTrigger(me.getEvent())) &&
        ((!mxEvent.isControlDown(me.getEvent()) &&
          mxEvent.isRightMouseButton(me.getEvent())) ||
          mxEvent.isMiddleMouseButton(me.getEvent())))
    );
  };

  //// Enables scrollbars and sets cursor style for the container
  graph.container.setAttribute('tabindex', '0');
  graph.container.style.cursor = 'default';

  //// Workaround for page scroll if embedded via iframe
  if (window.self === window.top && graph.container.parentNode != null) {
    try {
      graph.container.focus();
    } catch (e) {
      //// ignores error in old versions of IE
    }
  }

  //// Keeps graph container focused on mouse down
  var graphFireMouseEvent = graph.fireMouseEvent;
  graph.fireMouseEvent = function (evtName, me, sender) {
    if (evtName == mxEvent.MOUSE_DOWN) {
      this.container.focus();
    }

    graphFireMouseEvent.apply(this, arguments);
  };

  //// Configures automatic expand on mouseover
  graph.popupMenuHandler.autoExpand = true;

  //// Hides context menu
  //// mxEvent.addGestureListeners(
  ////   document,
  ////   mxUtils.bind(this, function (evt) {
  ////     graph.popupMenuHandler.hideMenu();
  ////   })
  //// );

  //// Create handler for key events
  //// this.keyHandler = this.createKeyHandler(editor);

  //// Getter for key handler
  //// this.getKeyHandler = function () {
  ////   return keyHandler;
  //// };

  //// Stores the current style and assigns it to new cells
  var styles = [
    'rounded',
    'shadow',
    'glass',
    'dashed',
    'dashPattern',
    'comic',
    'labelBackgroundColor'
  ];
  var connectStyles = [
    'shape',
    'edgeStyle',
    'curved',
    'rounded',
    'elbow',
    'comic',
    'jumpStyle',
    'jumpSize'
  ];

  //// Note: Everything that is not in styles is ignored (styles is augmented below)
  this.setDefaultStyle = function (cell) {
    try {
      var state = graph.view.getState(cell);

      if (state != null) {
        //// Ignores default styles
        var clone = cell.clone();
        clone.style = '';
        var defaultStyle = graph.getCellStyle(clone);
        var values = [];
        var keys = [];

        for (var key in state.style) {
          if (defaultStyle[key] != state.style[key]) {
            values.push(state.style[key]);
            keys.push(key);
          }
        }

        //// Handles special case for value "none"
        var cellStyle = graph.getModel().getStyle(state.cell);
        var tokens = cellStyle != null ? cellStyle.split(';') : [];

        for (var i = 0; i < tokens.length; i++) {
          var tmp = tokens[i];
          var pos = tmp.indexOf('=');

          if (pos >= 0) {
            var key = tmp.substring(0, pos);
            var value = tmp.substring(pos + 1);

            if (defaultStyle[key] != null && value == 'none') {
              values.push(value);
              keys.push(key);
            }
          }
        }

        //// Resets current style
        if (graph.getModel().isEdge(state.cell)) {
          graph.currentEdgeStyle = {};
        } else {
          graph.currentVertexStyle = {};
        }

        this.fireEvent(
          new mxEventObject(
            'styleChanged',
            'keys',
            keys,
            'values',
            values,
            'cells',
            [state.cell]
          )
        );
      }
    } catch (e) {
      this.handleError(e);
    }
  };

  this.clearDefaultStyle = function () {
    graph.currentEdgeStyle = mxUtils.clone(graph.defaultEdgeStyle);
    graph.currentVertexStyle = mxUtils.clone(graph.defaultVertexStyle);

    //// Updates UI
    this.fireEvent(
      new mxEventObject('styleChanged', 'keys', [], 'values', [], 'cells', [])
    );
  };

  //// Keys that should be ignored if the cell has a value (known: new default for all cells is html=1 so
  //// for the html key this effecticely only works for edges inserted via the connection handler)
  var valueStyles = ['fontFamily', 'fontSize', 'fontColor'];

  //// Keys that always update the current edge style regardless of selection
  var alwaysEdgeStyles = [
    'edgeStyle',
    'startArrow',
    'startFill',
    'startSize',
    'endArrow',
    'endFill',
    'endSize'
  ];

  //// Keys that are ignored together (if one appears all are ignored)
  var keyGroups = [
    [
      'startArrow',
      'startFill',
      'startSize',
      'sourcePerimeterSpacing',
      'endArrow',
      'endFill',
      'endSize',
      'targetPerimeterSpacing'
    ],
    ['strokeColor', 'strokeWidth'],
    ['fillColor', 'gradientColor'],
    valueStyles,
    ['opacity'],
    ['align'],
    ['html']
  ];

  //// Adds all keys used above to the styles array
  for (var i = 0; i < keyGroups.length; i++) {
    for (var j = 0; j < keyGroups[i].length; j++) {
      styles.push(keyGroups[i][j]);
    }
  }

  for (var i = 0; i < connectStyles.length; i++) {
    if (mxUtils.indexOf(styles, connectStyles[i]) < 0) {
      styles.push(connectStyles[i]);
    }
  }

  //// Implements a global current style for edges and vertices that is applied to new cells
  var insertHandler = function (cells, asText) {
    var model = graph.getModel();

    model.beginUpdate();
    try {
      //// Applies only basic text styles
      if (asText) {
        var edge = model.isEdge(cell);
        var current = edge ? graph.currentEdgeStyle : graph.currentVertexStyle;
        var textStyles = ['fontSize', 'fontFamily', 'fontColor'];

        for (var j = 0; j < textStyles.length; j++) {
          var value = current[textStyles[j]];

          if (value != null) {
            graph.setCellStyles(textStyles[j], value, cells);
          }
        }
      } else {
        for (var i = 0; i < cells.length; i++) {
          var cell = cells[i];

          //// Removes styles defined in the cell style from the styles to be applied
          var cellStyle = model.getStyle(cell);
          var tokens = cellStyle != null ? cellStyle.split(';') : [];
          var appliedStyles = styles.slice();

          for (var j = 0; j < tokens.length; j++) {
            var tmp = tokens[j];
            var pos = tmp.indexOf('=');

            if (pos >= 0) {
              var key = tmp.substring(0, pos);
              var index = mxUtils.indexOf(appliedStyles, key);

              if (index >= 0) {
                appliedStyles.splice(index, 1);
              }

              //// Handles special cases where one defined style ignores other styles
              for (var k = 0; k < keyGroups.length; k++) {
                var group = keyGroups[k];

                if (mxUtils.indexOf(group, key) >= 0) {
                  for (var l = 0; l < group.length; l++) {
                    var index2 = mxUtils.indexOf(appliedStyles, group[l]);

                    if (index2 >= 0) {
                      appliedStyles.splice(index2, 1);
                    }
                  }
                }
              }
            }
          }

          //// Applies the current style to the cell
          var edge = model.isEdge(cell);
          var current = edge
            ? graph.currentEdgeStyle
            : graph.currentVertexStyle;
          var newStyle = model.getStyle(cell);

          for (var j = 0; j < appliedStyles.length; j++) {
            var key = appliedStyles[j];
            var styleValue = current[key];

            if (styleValue != null && (key != 'shape' || edge)) {
              //// Special case: Connect styles are not applied here but in the connection handler
              if (!edge || mxUtils.indexOf(connectStyles, key) < 0) {
                newStyle = mxUtils.setStyle(newStyle, key, styleValue);
              }
            }
          }

          model.setStyle(cell, newStyle);
        }
      }
    } finally {
      model.endUpdate();
    }
  };

  graph.addListener('cellsInserted', function (sender, evt) {
    insertHandler(evt.getProperty('cells'));
  });

  graph.addListener('textInserted', function (sender, evt) {
    insertHandler(evt.getProperty('cells'), true);
  });

  graph.connectionHandler.addListener(mxEvent.CONNECT, function (sender, evt) {
    var cells = [evt.getProperty('cell')];

    if (evt.getProperty('terminalInserted')) {
      cells.push(evt.getProperty('terminal'));
    }

    insertHandler(cells);
  });

  this.addListener(
    'styleChanged',
    mxUtils.bind(this, function (sender, evt) {
      //// Checks if edges and/or vertices were modified
      var cells = evt.getProperty('cells');
      var vertex = false;
      var edge = false;

      if (cells.length > 0) {
        for (var i = 0; i < cells.length; i++) {
          vertex = graph.getModel().isVertex(cells[i]) || vertex;
          edge = graph.getModel().isEdge(cells[i]) || edge;

          if (edge && vertex) {
            break;
          }
        }
      } else {
        vertex = true;
        edge = true;
      }

      var keys = evt.getProperty('keys');
      var values = evt.getProperty('values');

      for (var i = 0; i < keys.length; i++) {
        var common = mxUtils.indexOf(valueStyles, keys[i]) >= 0;

        //// Ignores transparent stroke colors
        if (
          keys[i] != 'strokeColor' ||
          (values[i] != null && values[i] != 'none')
        ) {
          //// Special case: Edge style and shape
          if (mxUtils.indexOf(connectStyles, keys[i]) >= 0) {
            if (edge || mxUtils.indexOf(alwaysEdgeStyles, keys[i]) >= 0) {
              if (values[i] == null) {
                delete graph.currentEdgeStyle[keys[i]];
              } else {
                graph.currentEdgeStyle[keys[i]] = values[i];
              }
            }
            //// Uses style for vertex if defined in styles
            else if (vertex && mxUtils.indexOf(styles, keys[i]) >= 0) {
              if (values[i] == null) {
                delete graph.currentVertexStyle[keys[i]];
              } else {
                graph.currentVertexStyle[keys[i]] = values[i];
              }
            }
          } else if (mxUtils.indexOf(styles, keys[i]) >= 0) {
            if (vertex || common) {
              if (values[i] == null) {
                delete graph.currentVertexStyle[keys[i]];
              } else {
                graph.currentVertexStyle[keys[i]] = values[i];
              }
            }

            if (
              edge ||
              common ||
              mxUtils.indexOf(alwaysEdgeStyles, keys[i]) >= 0
            ) {
              if (values[i] == null) {
                delete graph.currentEdgeStyle[keys[i]];
              } else {
                graph.currentEdgeStyle[keys[i]] = values[i];
              }
            }
          }
        }
      }

    })
  );

  graph.addListener(mxEvent.CELL_CONNECTED, function (sender, evt) {
    let finishAdd = !evt.getProperty('source');
    if (finishAdd) {
      let edge = evt.getProperty('edge');
      let source = edge.source;
      let target = edge.target;
      console.log('Connected', 'source', source, 'target', target);
      //// create dependency and fire state change
    } else {
      console.log('Ignored one side edge add');
    }
  });

  //// Makes sure the current layer is visible when cells are added
  graph.addListener(mxEvent.CELLS_ADDED, function (sender, evt) {
    var cells = evt.getProperty('cells');
    var parent = evt.getProperty('parent');

    if (
      graph.getModel().isLayer(parent) &&
      !graph.isCellVisible(parent) &&
      cells != null &&
      cells.length > 0
    ) {
      graph.getModel().setVisible(parent, true);
    }
  });

  //// Global handler to hide the current menu
  this.gestureHandler = mxUtils.bind(this, function (evt) {
    if (
      this.currentMenu != null &&
      mxEvent.getSource(evt) != this.currentMenu.div
    ) {
      this.hideCurrentMenu();
    }
  });

  mxEvent.addGestureListeners(document, this.gestureHandler);

  //// Updates the editor UI after the window has been resized or the orientation changes
  //// Timeout is workaround for old IE versions which have a delay for DOM client sizes.
  //// Should not use delay > 0 to avoid handle multiple repaints during window resize
  this.resizeHandler = mxUtils.bind(this, function () {
    window.setTimeout(
      mxUtils.bind(this, function () {
        if (this.editor.graph != null) {
          this.refresh();
        }
      }),
      0
    );
  });

  mxEvent.addListener(window, 'resize', this.resizeHandler);

  this.orientationChangeHandler = mxUtils.bind(this, function () {
    this.refresh();
  });

  mxEvent.addListener(
    window,
    'orientationchange',
    this.orientationChangeHandler
  );

  //// Workaround for bug on iOS see
  //// http:////stackoverflow.com/questions/19012135/ios-7-ipad-safari-landscape-innerheight-outerheight-layout-issue
  if (mxClient.IS_IOS && !window.navigator.standalone) {
    this.scrollHandler = mxUtils.bind(this, function () {
      window.scrollTo(0, 0);
    });

    mxEvent.addListener(window, 'scroll', this.scrollHandler);
  }

  /**
   * Sets the initial scrollbar locations after a file was loaded.
   */
  this.editor.addListener(
    'resetGraphView',
    mxUtils.bind(this, function () {
      this.resetScrollbars();
    })
  );

  /**
   * Repaints the grid.
   */
  this.addListener(
    'gridEnabledChanged',
    mxUtils.bind(this, function () {
      graph.view.validateBackground();
    })
  );

  this.addListener(
    'backgroundColorChanged',
    mxUtils.bind(this, function () {
      graph.view.validateBackground();
    })
  );

  /**
   * Repaints the grid.
   */
  graph.addListener(
    'gridSizeChanged',
    mxUtils.bind(this, function () {
      if (graph.isGridEnabled()) {
        graph.view.validateBackground();
      }
    })
  );

  //// Resets UI, updates action and menu states
  this.editor.resetGraph();
  this.init();
  this.open();
};

//// Extends mxEventSource
mxUtils.extend(EditorUi, mxEventSource);

/**
 * Global config that specifies if the compact UI elements should be used.
 */
EditorUi.compactUi = true;

/**
 * Specifies the size of the split bar.
 */
EditorUi.prototype.splitSize =
  mxClient.IS_TOUCH || mxClient.IS_POINTER ? 12 : 8;

/**
 * Specifies the width of the format panel should be enabled. Default is true.
 */
EditorUi.prototype.formatEnabled = false;

/**
 * Specifies the width of the format panel. Default is 240.
 */
EditorUi.prototype.formatWidth = 240;

/**
 * Specifies the position of the horizontal split bar. Default is 240 or 118 for
 * screen widths <= 640px.
 */
EditorUi.prototype.hsplitPosition = screen.width <= 640 ? 118 : 144;

/**
 * Specifies if animations are allowed in <executeLayout>. Default is true.
 */
EditorUi.prototype.allowAnimation = true;

/**
 * Specifies if animations are allowed in <executeLayout>. Default is true.
 */
EditorUi.prototype.lightboxMaxFitScale = 2;

/**
 * Specifies if animations are allowed in <executeLayout>. Default is true.
 */
EditorUi.prototype.lightboxVerticalDivider = 4;

/**
 * Specifies if single click on horizontal split should collapse sidebar. Default is false.
 */
EditorUi.prototype.hsplitClickEnabled = false;

EditorUi.prototype.graphEditor = null;

/**
 * Installs the listeners to update the action states.
 */
EditorUi.prototype.init = function () {
  /**
   * Keypress starts immediate editing on selection cell
   */
  let graph = this.editor.graph;

  mxEvent.addListener(
    graph.container,
    'keydown',
    mxUtils.bind(this, function (evt) {
      this.onKeyDown(evt);
    })
  );
  mxEvent.addListener(
    graph.container,
    'keypress',
    mxUtils.bind(this, function (evt) {
      this.onKeyPress(evt);
    })
  );

  //// Updates action states
  //// this.addUndoListener();
  //// 删除离开确认提示
  //// this.addBeforeUnloadListener();

  graph.getSelectionModel().addListener(
    mxEvent.CHANGE,
    mxUtils.bind(this, function () {
      this.updateActionStates();
    })
  );

  graph.getModel().addListener(
    mxEvent.CHANGE,
    mxUtils.bind(this, function () {
      this.updateActionStates();
    })
  );

  //// Changes action states after change of default parent
  let graphSetDefaultParent = graph.setDefaultParent;
  let ui = this;

  this.editor.graph.setDefaultParent = function () {
    graphSetDefaultParent.apply(this, arguments);
    ui.updateActionStates();
  };

  //// Hack to make editLink available in vertex handler
  graph.editLink = ui.actions.get('editLink').funct;
  //// graph.bindXMLName = ui.actions.get('bindXMLName').funct; ////新增

  this.updateActionStates();
  //// this.initClipboard();
  this.initCanvas();

};

/**
 * Returns true if the given event should start editing. This implementation returns true.
 */
EditorUi.prototype.onKeyDown = function (evt) {
  var graph = this.editor.graph;
  //// Tab selects next cell
  if (evt.which == 9 && graph.isEnabled() && !mxEvent.isAltDown(evt)) {
    if (graph.isEditing()) {
      graph.stopEditing(false);
    } else {
      graph.selectCell(!mxEvent.isShiftDown(evt));
    }
    mxEvent.consume(evt);
  }
};

/**
 * Returns true if the given event should start editing. This implementation returns true.
 */
EditorUi.prototype.onKeyPress = function (evt) {
  var graph = this.editor.graph;

  //// KNOWN: Focus does not work if label is empty in quirks mode
  if (
    this.isImmediateEditingEvent(evt) &&
    !graph.isEditing() &&
    !graph.isSelectionEmpty() &&
    evt.which !== 0 &&
    !mxEvent.isAltDown(evt) &&
    !mxEvent.isControlDown(evt) &&
    !mxEvent.isMetaDown(evt)
  ) {
    graph.escape();
    graph.startEditing();

    //// Workaround for FF where char is lost if cursor is placed before char
    if (mxClient.IS_FF) {
      var ce = graph.cellEditor;
      ce.textarea.innerHTML = String.fromCharCode(evt.which);

      //// Moves cursor to end of textarea
      var range = document.createRange();
      range.selectNodeContents(ce.textarea);
      range.collapse(false);
      var sel = window.getSelection();
      sel.removeAllRanges();
      sel.addRange(range);
    }
  }
};

/**
 * Returns true if the given event should start editing. This implementation returns true.
 */
EditorUi.prototype.isImmediateEditingEvent = function (evt) {
  return true;
};
/**
 * Initializes the infinite canvas.
 */
EditorUi.prototype.lazyZoomDelay = 20;

/**
 * Initializes the infinite canvas.
 */
EditorUi.prototype.initCanvas = function () {
  //// Initial page layout view, scrollBuffer and timer-based scrolling
  var graph = this.editor.graph;
  graph.timerAutoScroll = true;

  /**
   * Returns the padding for pages in page view with scrollbars.
   */
  graph.getPagePadding = function () {
    //// 修改画布的间隔大小
    return new mxPoint(
      0, 0
    );
  };

  //// Fits the number of background pages to the graph
  graph.view.getBackgroundPageBounds = function () {
    var layout = this.graph.getPageLayout();
    var page = this.graph.getPageSize();

    return new mxRectangle(
      this.scale * (this.translate.x + layout.x * page.width),
      this.scale * (this.translate.y + layout.y * page.height),
      this.scale * layout.width * page.width,
      this.scale * layout.height * page.height
    );
  };

  graph.getPreferredPageSize = function (bounds, width, height) {
    var pages = this.getPageLayout();
    var size = this.getPageSize();

    return new mxRectangle(
      0,
      0,
      pages.width * size.width,
      pages.height * size.height
    );
  };

  //// Scales pages/graph to fit available size
  var resize = null;
  var ui = this;

  if (this.editor.extendCanvas) {
    /**
     * Guesses autoTranslate to avoid another repaint (see below).
     * Works if only the scale of the graph changes or if pages
     * are visible and the visible pages do not change.
     */
    var graphViewValidate = graph.view.validate;
    graph.view.validate = function () {
      if (
        this.graph.container != null &&
        mxUtils.hasScrollbars(this.graph.container)
      ) {
        var pad = this.graph.getPagePadding();
        var size = this.graph.getPageSize();

        //// Updating scrollbars here causes flickering in quirks and is not needed
        //// if zoom method is always used to set the current scale on the graph.
        var tx = this.translate.x;
        var ty = this.translate.y;
        this.translate.x = pad.x - (this.x0 || 0) * size.width;
        this.translate.y = pad.y - (this.y0 || 0) * size.height;
      }

      graphViewValidate.apply(this, arguments);
    };

    var graphSizeDidChange = graph.sizeDidChange;
    graph.sizeDidChange = function () {
      if (this.container != null && mxUtils.hasScrollbars(this.container)) {
        var pages = this.getPageLayout();
        var pad = this.getPagePadding();
        var size = this.getPageSize();

        //// Updates the minimum graph size
        var minw = Math.ceil(2 * pad.x + pages.width * size.width);
        var minh = Math.ceil(2 * pad.y + pages.height * size.height);

        var min = graph.minimumGraphSize;

        //// LATER: Fix flicker of scrollbar size in IE quirks mode
        //// after delayed call in window.resize event handler
        if (min == null || min.width != minw || min.height != minh) {
          graph.minimumGraphSize = new mxRectangle(0, 0, minw, minh);
        }

        //// Updates auto-translate to include padding and graph size
        var dx = pad.x - pages.x * size.width;
        var dy = pad.y - pages.y * size.height;

        if (
          !this.autoTranslate &&
          (this.view.translate.x != dx || this.view.translate.y != dy)
        ) {
          this.autoTranslate = true;
          this.view.x0 = pages.x;
          this.view.y0 = pages.y;

          //// NOTE: THIS INVOKES THIS METHOD AGAIN. UNFORTUNATELY THERE IS NO WAY AROUND THIS SINCE THE
          //// BOUNDS ARE KNOWN AFTER THE VALIDATION AND SETTING THE TRANSLATE TRIGGERS A REVALIDATION.
          //// SHOULD MOVE TRANSLATE/SCALE TO VIEW.
          var tx = graph.view.translate.x;
          var ty = graph.view.translate.y;
          graph.view.setTranslate(dx, dy);

          //// LATER: Fix rounding errors for small zoom
          graph.container.scrollLeft += Math.round(
            (dx - tx) * graph.view.scale
          );
          graph.container.scrollTop += Math.round((dy - ty) * graph.view.scale);

          this.autoTranslate = false;

          return;
        }

        graphSizeDidChange.apply(this, arguments);
      } else {
        //// Fires event but does not invoke superclass
        this.fireEvent(
          new mxEventObject(mxEvent.SIZE, 'bounds', this.getGraphBounds())
        );
      }
    };
  }

  //// Accumulates the zoom factor while the rendering is taking place
  //// so that not the complete sequence of zoom steps must be painted
  graph.updateZoomTimeout = null;
  graph.cumulativeZoomFactor = 1;

  var cursorPosition = null;

  graph.lazyZoom = function (zoomIn) {
    if (this.updateZoomTimeout != null) {
      window.clearTimeout(this.updateZoomTimeout);
    }

    //// Switches to 1% zoom steps below 15%
    //// Lower bound depdends on rounding below
    if (zoomIn) {
      if (this.view.scale * this.cumulativeZoomFactor < 0.15) {
        this.cumulativeZoomFactor = (this.view.scale + 0.01) / this.view.scale;
      } else {
        //// Uses to 5% zoom steps for better grid rendering in webkit
        //// and to avoid rounding errors for zoom steps
        this.cumulativeZoomFactor *= this.zoomFactor;
        this.cumulativeZoomFactor =
          Math.round(this.view.scale * this.cumulativeZoomFactor * 20) /
          20 /
          this.view.scale;
      }
    } else {
      if (this.view.scale * this.cumulativeZoomFactor <= 0.15) {
        this.cumulativeZoomFactor = (this.view.scale - 0.01) / this.view.scale;
      } else {
        //// Uses to 5% zoom steps for better grid rendering in webkit
        //// and to avoid rounding errors for zoom steps
        this.cumulativeZoomFactor /= this.zoomFactor;
        this.cumulativeZoomFactor =
          Math.round(this.view.scale * this.cumulativeZoomFactor * 20) /
          20 /
          this.view.scale;
      }
    }

    this.cumulativeZoomFactor = Math.max(
      0.01,
      Math.min(this.view.scale * this.cumulativeZoomFactor, 160) /
      this.view.scale
    );

    this.updateZoomTimeout = window.setTimeout(
      mxUtils.bind(this, function () {
        var offset = mxUtils.getOffset(graph.container);
        var dx = 0;
        var dy = 0;

        if (cursorPosition != null) {
          dx = graph.container.offsetWidth / 2 - cursorPosition.x + offset.x;
          dy = graph.container.offsetHeight / 2 - cursorPosition.y + offset.y;
        }

        var prev = this.view.scale;
        this.zoom(this.cumulativeZoomFactor);
        var s = this.view.scale;

        if (s != prev) {
          if (resize != null) {
            ui.chromelessResize(
              false,
              null,
              dx * (this.cumulativeZoomFactor - 1),
              dy * (this.cumulativeZoomFactor - 1)
            );
          }

          if (mxUtils.hasScrollbars(graph.container) && (dx != 0 || dy != 0)) {
            graph.container.scrollLeft -= dx * (this.cumulativeZoomFactor - 1);
            graph.container.scrollTop -= dy * (this.cumulativeZoomFactor - 1);
          }
        }

        this.cumulativeZoomFactor = 1;
        this.updateZoomTimeout = null;
      }),
      this.lazyZoomDelay
    );
  };

  mxEvent.addMouseWheelListener(
    mxUtils.bind(this, function (evt, up) {
      //// Ctrl+wheel (or pinch on touchpad) is a native browser zoom event is OS X
      //// LATER: Add support for zoom via pinch on trackpad for Chrome in OS X
      if (
        (this.dialogs == null || this.dialogs.length == 0) &&
        graph.isZoomWheelEvent(evt)
      ) {
        var source = mxEvent.getSource(evt);

        while (source != null) {
          if (source == graph.container) {
            cursorPosition = new mxPoint(
              mxEvent.getClientX(evt),
              mxEvent.getClientY(evt)
            );
            graph.lazyZoom(up);
            mxEvent.consume(evt);

            return false;
          }

          source = source.parentNode;
        }
      }
    }),
    graph.container
  );
};

/**
 * Hook for allowing selection and context menu for certain events.
 */
EditorUi.prototype.isSelectionAllowed = function (evt) {
  return (
    mxEvent.getSource(evt).nodeName == 'SELECT'
  );
};

/**
 * Opens the current diagram via the window.opener if one exists.
 */
EditorUi.prototype.open = function () {
  //// Cross-domain window access is not allowed in FF, so if we
  //// were opened from another domain then this will fail.
  try {
    if (window.opener != null && window.opener.openFile != null) {
      window.opener.openFile.setConsumer(
        mxUtils.bind(this, function (xml, filename) {
          try {
            var doc = mxUtils.parseXml(xml);
            this.editor.setGraphXml(doc.documentElement);
            this.editor.setModified(false);
            this.editor.undoManager.clear();

            if (filename != null) {
              this.editor.setFilename(filename);
              this.updateDocumentTitle();
            }

            return;
          } catch (e) {
            mxUtils.alert(
              mxResources.get('invalidOrMissingFile') + ': ' + e.message
            );
          }
        })
      );
    }
  } catch (e) {
    //// ignore
  }

  //// Fires as the last step if no file was loaded
  this.editor.graph.view.validate();

  //// Required only in special cases where an initial file is opened
  //// and the minimumGraphSize changes and CSS must be updated.
  this.editor.graph.sizeDidChange();
  this.editor.fireEvent(new mxEventObject('resetGraphView'));
};

/**
 * Sets the current menu and element.
 */
EditorUi.prototype.setCurrentMenu = function (menu, elt) {
  this.currentMenuElt = elt;
  this.currentMenu = menu;
};

/**
 * Resets the current menu and element.
 */
EditorUi.prototype.resetCurrentMenu = function () {
  this.currentMenuElt = null;
  this.currentMenu = null;
};

/**
 * Hides and destroys the current menu.
 */
EditorUi.prototype.hideCurrentMenu = function () {
  if (this.currentMenu != null) {
    this.currentMenu.hideMenu();
    this.resetCurrentMenu();
  }
};

/**
 * Updates the document title.
 */
EditorUi.prototype.updateDocumentTitle = function () {
  var title = this.editor.getOrCreateFilename();

  if (this.editor.appName != null) {
    title += ' - ' + this.editor.appName;
  }

  document.title = title;
};

/**
 * Returns the URL for a copy of this editor with no state.
 */
EditorUi.prototype.redo = function () {
  try {
    var graph = this.editor.graph;

    if (graph.isEditing()) {
      document.execCommand('redo', false, null);
    } else {
      this.editor.undoManager.redo();
    }
  } catch (e) {
    //// ignore all errors
  }
};

/**
 * Returns the URL for a copy of this editor with no state.
 */
EditorUi.prototype.undo = function () {
  try {
    var graph = this.editor.graph;

    if (graph.isEditing()) {
      //// Stops editing and executes undo on graph if native undo
      //// does not affect current editing value
      var value = graph.cellEditor.textarea.innerHTML;
      document.execCommand('undo', false, null);

      if (value == graph.cellEditor.textarea.innerHTML) {
        graph.stopEditing(true);
        this.editor.undoManager.undo();
      }
    } else {
      this.editor.undoManager.undo();
    }
  } catch (e) {
    //// ignore all errors
  }
};

/**
 * Returns the URL for a copy of this editor with no state.
 */
EditorUi.prototype.canRedo = function () {
  return this.editor.graph.isEditing() || this.editor.undoManager.canRedo();
};

/**
 * Returns the URL for a copy of this editor with no state.
 */
EditorUi.prototype.canUndo = function () {
  return this.editor.graph.isEditing() || this.editor.undoManager.canUndo();
};

/**
 *
 */
EditorUi.prototype.getEditBlankXml = function () {
  return mxUtils.getXml(this.editor.getGraphXml());
};

/**
 * Returns the URL for a copy of this editor with no state.
 */
EditorUi.prototype.getUrl = function (pathname) {
  var href = pathname != null ? pathname : window.location.pathname;
  var parms = href.indexOf('?') > 0 ? 1 : 0;
  //// Removes template URL parameter for new blank diagram
  for (var key in urlParams) {
    if (parms == 0) {
      href += '?';
    } else {
      href += '&';
    }

    href += key + '=' + urlParams[key];
    parms++;
  }
  return href;
};

/**
 * Specifies if the graph has scrollbars.
 */
EditorUi.prototype.setScrollbars = function (value) {
  var graph = this.editor.graph;
  var prev = graph.container.style.overflow;
  graph.scrollbars = value;
  this.editor.updateGraphComponents();

  if (prev != graph.container.style.overflow) {
    if (graph.container.style.overflow == 'hidden') {
      var t = graph.view.translate;
      graph.view.setTranslate(
        t.x - graph.container.scrollLeft / graph.view.scale,
        t.y - graph.container.scrollTop / graph.view.scale
      );
      graph.container.scrollLeft = 0;
      graph.container.scrollTop = 0;
      graph.minimumGraphSize = null;
      graph.sizeDidChange();
    } else {
      var dx = graph.view.translate.x;
      var dy = graph.view.translate.y;

      graph.view.translate.x = 0;
      graph.view.translate.y = 0;
      graph.sizeDidChange();
      graph.container.scrollLeft -= Math.round(dx * graph.view.scale);
      graph.container.scrollTop -= Math.round(dy * graph.view.scale);
    }
  }

  this.fireEvent(new mxEventObject('scrollbarsChanged'));
};

/**
 * Returns true if the graph has scrollbars.
 */
EditorUi.prototype.hasScrollbars = function () {
  return this.editor.graph.scrollbars;
};

/**
 * Resets the state of the scrollbars.
 */
EditorUi.prototype.resetScrollbars = function () {
  var graph = this.editor.graph;

  if (!this.editor.extendCanvas) {
    graph.container.scrollTop = 0;
    graph.container.scrollLeft = 0;

    if (!mxUtils.hasScrollbars(graph.container)) {
      graph.view.setTranslate(0, 0);
    }
  } else if (!this.editor.isChromelessView()) {
    if (mxUtils.hasScrollbars(graph.container)) {
      if (graph.pageVisible) {
        var pad = graph.getPagePadding();
        graph.container.scrollTop =
          Math.floor(pad.y - this.editor.initialTopSpacing) - 1;
        graph.container.scrollLeft =
          Math.floor(
            Math.min(
              pad.x,
              (graph.container.scrollWidth - graph.container.clientWidth) / 2
            )
          ) - 1;

        //// Scrolls graph to visible area
        var bounds = graph.getGraphBounds();

        if (bounds.width > 0 && bounds.height > 0) {
          if (
            bounds.x >
            graph.container.scrollLeft + graph.container.clientWidth * 0.9
          ) {
            graph.container.scrollLeft = Math.min(
              bounds.x + bounds.width - graph.container.clientWidth,
              bounds.x - 10
            );
          }

          if (
            bounds.y >
            graph.container.scrollTop + graph.container.clientHeight * 0.9
          ) {
            graph.container.scrollTop = Math.min(
              bounds.y + bounds.height - graph.container.clientHeight,
              bounds.y - 10
            );
          }
        }
      } else {
        var bounds = graph.getGraphBounds();
        var width = Math.max(
          bounds.width,
          graph.scrollTileSize.width * graph.view.scale
        );
        var height = Math.max(
          bounds.height,
          graph.scrollTileSize.height * graph.view.scale
        );
        graph.container.scrollTop = Math.floor(
          Math.max(
            0,
            bounds.y - Math.max(20, (graph.container.clientHeight - height) / 4)
          )
        );
        graph.container.scrollLeft = Math.floor(
          Math.max(
            0,
            bounds.x - Math.max(0, (graph.container.clientWidth - width) / 2)
          )
        );
      }
    } else {
      //// This code is not actively used since the default for scrollbars is always true
      if (graph.pageVisible) {
        var b = graph.view.getBackgroundPageBounds();
        graph.view.setTranslate(
          Math.floor(
            Math.max(0, (graph.container.clientWidth - b.width) / 2) - b.x
          ),
          Math.floor(
            Math.max(0, (graph.container.clientHeight - b.height) / 2) - b.y
          )
        );
      } else {
        var bounds = graph.getGraphBounds();
        graph.view.setTranslate(
          Math.floor(
            Math.max(
              0,
              Math.max(0, (graph.container.clientWidth - bounds.width) / 2) -
              bounds.x
            )
          ),
          Math.floor(
            Math.max(
              0,
              Math.max(20, (graph.container.clientHeight - bounds.height) / 4)
            ) - bounds.y
          )
        );
      }
    }
  }
};

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setPageVisible = function (value) {
  var graph = this.editor.graph;
  var hasScrollbars = mxUtils.hasScrollbars(graph.container);
  var tx = 0;
  var ty = 0;

  if (hasScrollbars) {
    tx = graph.view.translate.x * graph.view.scale - graph.container.scrollLeft;
    ty = graph.view.translate.y * graph.view.scale - graph.container.scrollTop;
  }

  graph.pageVisible = value;
  graph.pageBreaksVisible = value;
  graph.preferPageSize = value;
  graph.view.validateBackground();

  //// Workaround for possible handle offset
  if (hasScrollbars) {
    var cells = graph.getSelectionCells();
    graph.clearSelection();
    graph.setSelectionCells(cells);
  }

  //// Calls updatePageBreaks
  graph.sizeDidChange();

  if (hasScrollbars) {
    graph.container.scrollLeft = graph.view.translate.x * graph.view.scale - tx;
    graph.container.scrollTop = graph.view.translate.y * graph.view.scale - ty;
  }

  this.fireEvent(new mxEventObject('pageViewChanged'));
};

/**
 * Change types
 */
function ChangePageSetup(ui, color, image, format) {
  this.ui = ui;
  this.color = color;
  this.previousColor = color;
  this.image = image;
  this.previousImage = image;
  this.format = format;
  this.previousFormat = format;

  //// Needed since null are valid values for color and image
  this.ignoreColor = false;
  this.ignoreImage = false;
}

/**
 * Implementation of the undoable page rename.
 */
ChangePageSetup.prototype.execute = function () {
  var graph = this.ui.editor.graph;

  if (!this.ignoreColor) {
    this.color = this.previousColor;
    var tmp = graph.background;
    this.ui.setBackgroundColor(this.previousColor);
    this.previousColor = tmp;
  }

  if (!this.ignoreImage) {
    this.image = this.previousImage;
    var tmp = graph.backgroundImage;
    this.ui.setBackgroundImage(this.previousImage);
    this.previousImage = tmp;
  }

  if (this.previousFormat != null) {
    this.format = this.previousFormat;
    var tmp = graph.pageFormat;

    if (
      this.previousFormat.width != tmp.width ||
      this.previousFormat.height != tmp.height
    ) {
      this.ui.setPageFormat(this.previousFormat);
      this.previousFormat = tmp;
    }
  }

  if (
    this.foldingEnabled != null &&
    this.foldingEnabled != this.ui.editor.graph.foldingEnabled
  ) {
    this.ui.setFoldingEnabled(this.foldingEnabled);
    this.foldingEnabled = !this.foldingEnabled;
  }
};

//// Registers codec for ChangePageSetup
(function () {
  var codec = new mxObjectCodec(new ChangePageSetup(), [
    'ui',
    'previousColor',
    'previousImage',
    'previousFormat'
  ]);

  codec.afterDecode = function (dec, node, obj) {
    obj.previousColor = obj.color;
    obj.previousImage = obj.image;
    obj.previousFormat = obj.format;

    if (obj.foldingEnabled != null) {
      obj.foldingEnabled = !obj.foldingEnabled;
    }

    return obj;
  };

  mxCodecRegistry.register(codec);
})();

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setBackgroundColor = function (value) {
  this.editor.graph.background = value;
  this.editor.graph.view.validateBackground();

  this.fireEvent(new mxEventObject('backgroundColorChanged'));
};

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setFoldingEnabled = function (value) {
  this.editor.graph.foldingEnabled = value;
  this.editor.graph.view.revalidate();

  this.fireEvent(new mxEventObject('foldingEnabledChanged'));
};

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setPageFormat = function (value) {
  this.editor.graph.pageFormat = value;

  if (!this.editor.graph.pageVisible) {
    this.actions.get('pageView').funct();
  } else {
    this.editor.graph.view.validateBackground();
    this.editor.graph.sizeDidChange();
  }

  this.fireEvent(new mxEventObject('pageFormatChanged'));
};

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setPageScale = function (value) {
  this.editor.graph.pageScale = value;

  if (!this.editor.graph.pageVisible) {
    this.actions.get('pageView').funct();
  } else {
    this.editor.graph.view.validateBackground();
    this.editor.graph.sizeDidChange();
  }

  this.fireEvent(new mxEventObject('pageScaleChanged'));
};

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setGridColor = function (value) {
  this.editor.graph.view.gridColor = value;
  this.editor.graph.view.validateBackground();
  this.fireEvent(new mxEventObject('gridColorChanged'));
};

/**
 * Updates the states of the given undo/redo items.
 */
//// EditorUi.prototype.addUndoListener = function () {
////   var undo = this.actions.get('undo');
////   var redo = this.actions.get('redo');

////   var undoMgr = this.editor.undoManager;

////   var undoListener = mxUtils.bind(this, function () {
////     undo.setEnabled(this.canUndo());
////     redo.setEnabled(this.canRedo());
////   });

////   undoMgr.addListener(mxEvent.ADD, undoListener);
////   undoMgr.addListener(mxEvent.UNDO, undoListener);
////   undoMgr.addListener(mxEvent.REDO, undoListener);
////   undoMgr.addListener(mxEvent.CLEAR, undoListener);

////   //// Overrides cell editor to update action states
////   var cellEditorStartEditing = this.editor.graph.cellEditor.startEditing;

////   this.editor.graph.cellEditor.startEditing = function () {
////     cellEditorStartEditing.apply(this, arguments);
////     undoListener();
////   };

////   var cellEditorStopEditing = this.editor.graph.cellEditor.stopEditing;

////   this.editor.graph.cellEditor.stopEditing = function (cell, trigger) {
////     cellEditorStopEditing.apply(this, arguments);
////     undoListener();
////   };

////   //// Updates the button states once
////   undoListener();
//// };

/**
 * Updates the states of the given toolbar items based on the selection.
 */
EditorUi.prototype.updateActionStates = function () {
  var graph = this.editor.graph;
  var selected = !graph.isSelectionEmpty();
  var vertexSelected = false;
  var edgeSelected = false;

  var cells = graph.getSelectionCells();

  if (cells != null) {
    for (var i = 0; i < cells.length; i++) {
      var cell = cells[i];

      if (graph.getModel().isEdge(cell)) {
        edgeSelected = true;
      }

      if (graph.getModel().isVertex(cell)) {
        vertexSelected = true;
      }

      if (edgeSelected && vertexSelected) {
        break;
      }
    }
  }

  //// Updates action states
  var actions = [
    'cut',
    'copy',
    'bold',
    'italic',
    'underline',
    //// 'bindingDeviceId',
    //// 'bindXMLName',
    'delete',
    'duplicate',
    //// 'editStyle',
    //// 'editTooltip',
    'editLink',
    'backgroundColor',
    'borderColor',
    'edit',
    'toFront',
    'toBack',
    'lockUnlock',
    'solid',
    'dashed',
    'pasteSize',
    'dotted',
    'fillColor',
    'gradientColor',
    'shadow',
    'fontColor',
    'formattedText',
    'rounded',
    'toggleRounded',
    'sharp',
    'strokeColor'
  ];

  for (var i = 0; i < actions.length; i++) {
    if ( actions[i] != 'dashed' ) {
      //// console.log(actions[i]);
      this.actions.get(actions[i]).setEnabled(selected);
    }
  }

  this.actions
    .get('setAsDefaultStyle')
    .setEnabled(graph.getSelectionCount() == 1);
  this.actions.get('clearWaypoints').setEnabled(!graph.isSelectionEmpty());
  this.actions.get('copySize').setEnabled(graph.getSelectionCount() == 1);
  this.actions.get('turn').setEnabled(!graph.isSelectionEmpty());
  this.actions.get('curved').setEnabled(edgeSelected);
  this.actions.get('wordWrap').setEnabled(vertexSelected);
  this.actions.get('autosize').setEnabled(vertexSelected);
  var oneVertexSelected = vertexSelected && graph.getSelectionCount() == 1;
  this.actions
    .get('group')
    .setEnabled(
      graph.getSelectionCount() > 1 ||
      (oneVertexSelected && !graph.isContainer(graph.getSelectionCell()))
    );
  this.actions
    .get('ungroup')
    .setEnabled(
      graph.getSelectionCount() == 1 &&
      (graph.getModel().getChildCount(graph.getSelectionCell()) > 0 ||
        (oneVertexSelected && graph.isContainer(graph.getSelectionCell())))
    );
  this.actions
    .get('removeFromGroup')
    .setEnabled(
      oneVertexSelected &&
      graph
        .getModel()
        .isVertex(graph.getModel().getParent(graph.getSelectionCell()))
    );

  //// Updates menu states
  var state = graph.view.getState(graph.getSelectionCell());

  this.actions
    .get('collapsible')
    .setEnabled(
      vertexSelected &&
      (graph.isContainer(graph.getSelectionCell()) ||
        graph.model.getChildCount(graph.getSelectionCell()) > 0)
    );
  this.actions.get('home').setEnabled(graph.view.currentRoot != null);
  this.actions.get('exitGroup').setEnabled(graph.view.currentRoot != null);
  this.actions
    .get('enterGroup')
    .setEnabled(
      graph.getSelectionCount() == 1 &&
      graph.isValidRoot(graph.getSelectionCell())
    );
  var foldable =
    graph.getSelectionCount() == 1 &&
    graph.isCellFoldable(graph.getSelectionCell());
  this.actions.get('expand').setEnabled(foldable);
  this.actions.get('collapse').setEnabled(foldable);
  this.actions.get('editLink').setEnabled(graph.getSelectionCount() == 1);
  this.actions
    .get('openLink')
    .setEnabled(
      graph.getSelectionCount() == 1 &&
      graph.getLinkForCell(graph.getSelectionCell()) != null
    );
  this.actions.get('guides').setEnabled(graph.isEnabled());
  this.actions
    .get('grid')
    .setEnabled(!this.editor.chromeless || this.editor.editable);

  var unlocked =
    graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent());
  this.actions.get('selectVertices').setEnabled(unlocked);
  this.actions.get('selectEdges').setEnabled(unlocked);
  this.actions.get('selectAll').setEnabled(unlocked);
  this.actions.get('selectNone').setEnabled(unlocked);

  //// this.updatePasteActionStates();
};

/**
 * Refreshes the viewport.
 */
EditorUi.prototype.refresh = function (sizeDidChange) {
  sizeDidChange = sizeDidChange != null ? sizeDidChange : true;

  var quirks =
    mxClient.IS_IE &&
    (document.documentMode == null || document.documentMode == 5);
  var w = this.container.clientWidth;
  var h = this.container.clientHeight;

  if (this.container == document.body) {
    w = document.body.clientWidth || document.documentElement.clientWidth;
    h = quirks
      ? document.body.clientHeight || document.documentElement.clientHeight
      : document.documentElement.clientHeight;
  }

  //// Workaround for bug on iOS see
  //// http:////stackoverflow.com/questions/19012135/ios-7-ipad-safari-landscape-innerheight-outerheight-layout-issue
  //// FIXME: Fix if footer visible
  var off = 0;

  if (mxClient.IS_IOS && !window.navigator.standalone) {
    if (window.innerHeight != document.documentElement.clientHeight) {
      off = document.documentElement.clientHeight - window.innerHeight;
      window.scrollTo(0, 0);
    }
  }

  var effHsplitPosition = Math.max(
    0,
    Math.min(this.hsplitPosition, w - this.splitSize - 20)
  );
  var tmp = 0;

  if (tmp > 0 && !mxClient.IS_QUIRKS) {
    tmp += 1;
  }

  var fw = 0;

  this.diagramContainer.style.left =
    this.hsplit.parentNode != null
      ? effHsplitPosition + this.splitSize + 'px'
      : '0px';

  this.hsplit.style.bottom = 0 + 'px';
  this.hsplit.style.left = effHsplitPosition + 'px';

  if (this.tabContainer != null) {
    this.tabContainer.style.left = this.diagramContainer.style.left;
  }

  if (quirks) {

    this.diagramContainer.style.width =
      this.hsplit.parentNode != null
        ? Math.max(0, w - effHsplitPosition - this.splitSize - fw) + 'px'
        : w + 'px';
    var diagramHeight = Math.max(
      0,
      h  - this.toolbarHeight
    );

    if (this.tabContainer != null) {
      this.tabContainer.style.width = this.diagramContainer.style.width;
      this.tabContainer.style.bottom =  off + 'px';
      diagramHeight -= this.tabContainer.clientHeight;
    }

    this.diagramContainer.style.height = diagramHeight + 'px';
    this.hsplit.style.height = diagramHeight + 'px';
  } else {

    this.diagramContainer.style.right = fw + 'px';
    var th = 0;

    if (this.tabContainer != null) {
      this.tabContainer.style.bottom = off + 'px';
      this.tabContainer.style.right = this.diagramContainer.style.right;
      th = this.tabContainer.clientHeight;
    }

    this.diagramContainer.style.bottom = 0 + 'px';
  }

  if (sizeDidChange) {
    this.editor.graph.sizeDidChange();
  }
};

/**
 * Creates the required containers.
 */
EditorUi.prototype.createTabContainer = function () {
  return null;
};

/**
 * Creates the required containers.
 */
EditorUi.prototype.createDivs = function () {
  this.diagramContainer = this.createDiv('geDiagramContainer');
  this.hsplit = this.createDiv('geHsplit');
  this.hsplit.setAttribute('title', mxResources.get('collapseExpand'));

  this.diagramContainer.style.right = '0px';
  this.diagramContainer.style.top = '0px';

  this.hsplit.style.width = this.splitSize + 'px';

  if (!this.editor.chromeless) {
    this.tabContainer = this.createTabContainer();
  } else {
    this.diagramContainer.style.border = 'none';
  }
};

/**
 * Creates the required containers.
 */
EditorUi.prototype.createUi = function () {
  //// Creates menubar

  this.container.appendChild(this.diagramContainer);

  if (this.container != null && this.tabContainer != null) {
    this.container.appendChild(this.tabContainer);
  }

};

/**
 * Creates a new toolbar for the given container.
 */
EditorUi.prototype.createStatusContainer = function () {
  var container = document.createElement('a');
  container.className = 'geItem geStatus';
  if (screen.width < 420) {
    container.style.maxWidth = Math.max(20, screen.width - 320) + 'px';
    container.style.overflow = 'hidden';
  }
  return container;
};

/**
 * Creates a new toolbar for the given container.
 */
//// 页面内容是否修改了，导航栏显示的文字:修改未保存,点击此处保存./所有修改已保存。
EditorUi.prototype.setStatusText = function (value) {
  let inner = '';
  if (value == mxResources.get('modificationNotSavedClickHereToSave')) {
    inner =
      '<div class="geStatusAlert" style="cursor:pointer;overflow:hidden;">' +
      value +
      '</div>';
  }
  if (value == mxResources.get('allChangesSaved')) {
    inner = value;
  }
  this.statusContainer.innerHTML = inner;
};

/**
 * Creates the actual toolbar for the toolbar container.
 */
EditorUi.prototype.createDiv = function (classname) {
  var elt = document.createElement('div');
  elt.className = classname;
  return elt;
};

/**
 * Updates the states of the given undo/redo items.
 */
EditorUi.prototype.addSplitHandler = function (elt, horizontal, dx, onChange) {
  var start = null;
  var initial = null;
  var ignoreClick = true;
  var last = null;
  //// Disables built-in pan and zoom in IE10 and later
  if (mxClient.IS_POINTER) {
    elt.style.touchAction = 'none';
  }
  var getValue = mxUtils.bind(this, function () {
    var result = parseInt(horizontal ? elt.style.left : elt.style.bottom);
    //// Takes into account hidden footer
    if (!horizontal) {
      result = result + dx; 
    }
    return result;
  });

  function moveHandler(evt) {
    if (start != null) {
      var pt = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
      onChange(
        Math.max(
          0,
          initial + (horizontal ? pt.x - start.x : start.y - pt.y) - dx
        )
      );
      mxEvent.consume(evt);
      if (initial != getValue()) {
        ignoreClick = true;
        last = null;
      }
    }
  }

  function dropHandler(evt) {
    moveHandler(evt);
    initial = null;
    start = null;
  }

  mxEvent.addGestureListeners(elt, function (evt) {
    start = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
    initial = getValue();
    ignoreClick = false;
    mxEvent.consume(evt);
  });

  mxEvent.addListener(
    elt,
    'click',
    mxUtils.bind(this, function (evt) {
      if (!ignoreClick && this.hsplitClickEnabled) {
        var next = last != null ? last - dx : 0;
        last = getValue();
        onChange(next);
        mxEvent.consume(evt);
      }
    })
  );
  mxEvent.addGestureListeners(document, null, moveHandler, dropHandler);
  this.destroyFunctions.push(function () {
    mxEvent.removeGestureListeners(document, null, moveHandler, dropHandler);
  });
};

/**
 * Translates this point by the given vector.
 *
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
EditorUi.prototype.handleError = function (
  resp,
  title,
  fn,
  invokeFnOnClose,
  notFoundMessage
) {
  var e = resp != null && resp.error != null ? resp.error : resp;

  if (e != null || title != null) {
    var msg = mxUtils.htmlEntities(mxResources.get('unknownError'));
    var btn = mxResources.get('ok');
    title = title != null ? title : mxResources.get('error');
    if (e != null && e.message != null) {
      msg = mxUtils.htmlEntities(e.message);
    }
    this.showError(
      title,
      msg,
      btn,
      fn,
      null,
      null,
      null,
      null,
      null,
      null,
      null,
      null,
      invokeFnOnClose ? fn : null
    );
  } else if (fn != null) {
    fn();
  }
};

/**
 * Translates this point by the given vector.
 *
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
EditorUi.prototype.showError = function (
  title,
  msg,
  btn,
  fn,
  retry,
  btn2,
  fn2,
  btn3,
  fn3,
  w,
  h,
  hide,
  onClose
) {
  var dlg = new ErrorDialog(
    this,
    title,
    msg,
    btn || mxResources.get('ok'),
    fn,
    retry,
    btn2,
    fn2,
    hide,
    btn3,
    fn3
  );
  var lines = Math.ceil(msg != null ? msg.length / 50 : 1);
  this.showDialog(
    dlg.container,
    w || 340,
    h || 100 + lines * 20,
    true,
    false,
    onClose
  );
  dlg.init();
};

/**
 * Displays a print dialog.
 */
EditorUi.prototype.showDialog = function (
  elt,
  w,
  h,
  modal,
  closable,
  onClose,
  noScroll,
  transparent,
  onResize,
  ignoreBgClick
) {
  this.editor.graph.tooltipHandler.hideTooltip();
  if (this.dialogs == null) {
    this.dialogs = [];
  }
  this.dialog = new Dialog(
    this,
    elt,
    w,
    h,
    modal,
    closable,
    onClose,
    noScroll,
    transparent,
    onResize,
    ignoreBgClick
  );
  this.dialogs.push(this.dialog);
};

/**
 * Displays a print dialog.
 */
EditorUi.prototype.hideDialog = function (cancel, isEsc) {
  if (this.dialogs != null && this.dialogs.length > 0) {
    var dlg = this.dialogs.pop();
    if (dlg.close(cancel, isEsc) == false) {
      //// add the dialog back if dialog closing is cancelled
      this.dialogs.push(dlg);
      return;
    }
    this.dialog =
      this.dialogs.length > 0 ? this.dialogs[this.dialogs.length - 1] : null;
    this.editor.fireEvent(new mxEventObject('hideDialog'));

    if (
      this.dialog == null &&
      this.editor.graph.container.style.visibility != 'hidden'
    ) {
      window.setTimeout(
        mxUtils.bind(this, function () {
          if (
            this.editor.graph.isEditing() &&
            this.editor.graph.cellEditor.textarea != null
          ) {
            this.editor.graph.cellEditor.textarea.focus();
          } else {
            mxUtils.clearSelection();
            this.editor.graph.container.focus();
          }
        }),
        0
      );
    }
  }
};

/**
 * Extracs the graph model from the given HTML data from a data transfer event.
 */
EditorUi.prototype.extractGraphModelFromHtml = function (data) {
  var result = null;
  try {
    var idx = data.indexOf('&lt;mxGraphModel ');
    if (idx >= 0) {
      var idx2 = data.lastIndexOf('&lt;/mxGraphModel&gt;');
      if (idx2 > idx) {
        result = data
          .substring(idx, idx2 + 21)
          .replace(/&gt;/g, '>')
          .replace(/&lt;/g, '<')
          .replace(/\\&quot;/g, '"')
          .replace(/\n/g, '');
      }
    }
  } catch (e) {
    //// ignore
  }
  return result;
};

/**
 * Opens the given files in the editor.
 */
EditorUi.prototype.extractGraphModelFromEvent = function (evt) {
  var result = null;
  var data = null;
  if (evt != null) {
    var provider =
      evt.dataTransfer != null ? evt.dataTransfer : evt.clipboardData;
    if (provider != null) {
      if (document.documentMode == 10 || document.documentMode == 11) {
        data = provider.getData('Text');
      } else {
        data =
          mxUtils.indexOf(provider.types, 'text/html') >= 0
            ? provider.getData('text/html')
            : null;
        if (
          mxUtils.indexOf(
            provider.types,
            'text/plain' && (data == null || data.length == 0)
          )
        ) {
          data = provider.getData('text/plain');
        }
      }

      if (data != null) {
        data = Graph.zapGremlins(mxUtils.trim(data));
        //// Tries parsing as HTML document with embedded XML
        var xml = this.extractGraphModelFromHtml(data);
        if (xml != null) {
          data = xml;
        }
      }
    }
  }

  if (data != null && this.isCompatibleString(data)) {
    result = data;
  }
  return result;
};

/**
 * Hook for subclassers to return true if event data is a supported format.
 * This implementation always returns false.
 */
EditorUi.prototype.isCompatibleString = function (data) {
  return false;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
EditorUi.prototype.saveFile = function (forceDialog) {
  //// debugger

};

/**
 * Executes the given layout.
 */
EditorUi.prototype.executeLayout = function (exec, animate, post) {
  var graph = this.editor.graph;

  if (graph.isEnabled()) {
    graph.getModel().beginUpdate();
    try {
      exec();
    } catch (e) {
      throw e;
    } finally {
      //// Animates the changes in the graph model except
      //// for Camino, where animation is too slow
      if (
        this.allowAnimation &&
        animate &&
        navigator.userAgent.indexOf('Camino') < 0
      ) {
        //// New API for animating graph layout results asynchronously
        var morph = new mxMorphing(graph);
        morph.addListener(
          mxEvent.DONE,
          mxUtils.bind(this, function () {
            graph.getModel().endUpdate();

            if (post != null) {
              post();
            }
          })
        );

        morph.startAnimation();
      } else {
        graph.getModel().endUpdate();

        if (post != null) {
          post();
        }
      }
    }
  }
};

/**
 * Hides the current menu.
 */
EditorUi.prototype.showImageDialog = function (
  title,
  value,
  fn,
  ignoreExisting
) {
  var cellEditor = this.editor.graph.cellEditor;
  var selState = cellEditor.saveSelection();
  var newValue = mxUtils.prompt(title, value);
  cellEditor.restoreSelection(selState);

  if (newValue != null && newValue.length > 0) {
    var img = new Image();

    img.onload = function () {
      fn(newValue, img.width, img.height);
    };
    img.onerror = function () {
      fn(null);
      mxUtils.alert(mxResources.get('fileNotFound'));
    };

    img.src = newValue;
  } else {
    fn(null);
  }
};

/**
 * Hides the current menu.
 */
EditorUi.prototype.showBackgroundImageDialog = function (apply) {
  apply =
    apply != null
      ? apply
      : mxUtils.bind(this, function (image) {
        var change = new ChangePageSetup(this, null, image);
        change.ignoreColor = true;

        this.editor.graph.model.execute(change);
      });

  var newValue = mxUtils.prompt(mxResources.get('backgroundImage'), '');

  if (newValue != null && newValue.length > 0) {
    var img = new Image();

    img.onload = function () {
      apply(new mxImage(newValue, img.width, img.height));
    };
    img.onerror = function () {
      apply(null);
      mxUtils.alert(mxResources.get('fileNotFound'));
    };

    img.src = newValue;
  } else {
    apply(null);
  }
};

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setBackgroundImage = function (image) {
  this.editor.graph.setBackgroundImage(image);
  this.editor.graph.view.validateBackgroundImage();

  this.fireEvent(new mxEventObject('backgroundImageChanged'));
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
EditorUi.prototype.confirm = function (msg, okFn, cancelFn) {
  if (mxUtils.confirm(msg)) {
    if (okFn != null) {
      okFn();
    }
  } else if (cancelFn != null) {
    cancelFn();
  }
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
EditorUi.prototype.createOutline = function (wnd) {
  var outline = new mxOutline(this.editor.graph);
  outline.border = 20;

  mxEvent.addListener(window, 'resize', function () {
    outline.update();
  });

  this.addListener('pageFormatChanged', function () {
    outline.update();
  });

  return outline;
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
EditorUi.prototype.destroy = function () {
  if (this.editor != null) {
    this.editor.destroy();
    this.editor = null;
  }

  if (this.resizeHandler != null) {
    mxEvent.removeListener(window, 'resize', this.resizeHandler);
    this.resizeHandler = null;
  }

  if (this.gestureHandler != null) {
    mxEvent.removeGestureListeners(document, this.gestureHandler);
    this.gestureHandler = null;
  }

  if (this.orientationChangeHandler != null) {
    mxEvent.removeListener(
      window,
      'orientationchange',
      this.orientationChangeHandler
    );
    this.orientationChangeHandler = null;
  }

  if (this.scrollHandler != null) {
    mxEvent.removeListener(window, 'scroll', this.scrollHandler);
    this.scrollHandler = null;
  }

  if (this.destroyFunctions != null) {
    for (var i = 0; i < this.destroyFunctions.length; i++) {
      this.destroyFunctions[i]();
    }

    this.destroyFunctions = null;
  }

  var c = [
    this.diagramContainer,
    this.hsplit,
    this.layersDialog
  ];

  for (var i = 0; i < c.length; i++) {
    if (c[i] != null && c[i].parentNode != null) {
      c[i].parentNode.removeChild(c[i]);
    }
  }
};

export default EditorUi;
export { ChangePageSetup };
